/*
   This file is part of the 'Let's Do Stuff Together' project
   http://code.google.com/p/dostufftogether/

   Copyright 2010 Christoph Fuchs, Stefan Thaler

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package authentication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.OpenIDException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.InMemoryConsumerAssociationStore;
import org.openid4java.consumer.InMemoryNonceVerifier;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;

/**
 * Authenticates the user using OpenIdAuthentication. {link
 * 
 * @http://openid.net/specs/openid-authentication-2_0.html
 * 
 * @author Stefan
 * 
 */
public class OpenIdAuthenticationServlet extends HttpServlet {
	private static final Log log = LogFactory
			.getLog(OpenIdAuthenticationServlet.class);

	@Getter(AccessLevel.PUBLIC)
	@Setter(AccessLevel.PUBLIC) 
	private ServletContext context;

	@Getter(AccessLevel.PUBLIC)
	@Setter(AccessLevel.PUBLIC)
	private ConsumerManager manager;

	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		context = config.getServletContext();
		try {
			setManager(new ConsumerManager());
			getManager()
					.setAssociations(new InMemoryConsumerAssociationStore());
			getManager().setNonceVerifier(new InMemoryNonceVerifier(5000));
		} catch (ConsumerException e) {
			throw new ServletException(e);
		}
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		if ("true".equals(req.getParameter("is_return"))) {
			processReturn(req, resp);
		} else {
			String identifier = req.getParameter("openIdUrl");
			if (identifier != null) {
				log.trace("authenticating " + identifier);
				this.authRequest(identifier, req, resp);
			} else {
				log
						.trace("invalid authenication request - set openIdUrl parameter");
				resp.getWriter().write("false");
				// this.getServletContext().getRequestDispatcher("/index.jsp")
				// .forward(req, resp);
			}
		}
	}

	/**
	 * processes the returned AuthenticationRequest by the OpenId provider.
	 * 
	 * @param req
	 * @param resp
	 * @throws ServletException
	 * @throws IOException
	 */
	private void processReturn(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Identifier identifier = this.verifyResponse(req);
		log.debug("identifier: " + identifier);
		boolean loginSuccsesful = !(identifier == null);
		if (loginSuccsesful) { // login not successful
			req.setAttribute("identifier", identifier.getIdentifier());
			req.getSession(true).setAttribute("isAuthenticated", "true");
			resp.getWriter().write("true");
			resp.getWriter().flush();
			// this.getServletContext().getRequestDispatcher("/return.jsp")
			// .forward(req, resp);

		} else {
			req.getSession(true).setAttribute("isAuthenticated", "false");
			resp.getWriter().write("false");
			resp.getWriter().flush();
			// this.getServletContext().getRequestDispatcher("/index.jsp")
			// .forward(req, resp);

		}
	}

	// --- placing the authentication request ---
	public String authRequest(String userSuppliedString,
			HttpServletRequest httpReq, HttpServletResponse httpResp)
			throws IOException, ServletException {
		try {
			// configure the return_to URL where your application will receive
			// the authentication responses from the OpenID provider
			// String returnToUrl = "http://example.com/openid";
			String returnToUrl = httpReq.getRequestURL().toString()
					+ "?is_return=true";
			// perform discovery on the user-supplied identifier
			List discoveries = manager.discover(userSuppliedString);

			// attempt to associate with the OpenID provider
			// and retrieve one service endpoint for authentication
			DiscoveryInformation discovered = manager.associate(discoveries);

			// store the discovery information in the user's session
			httpReq.getSession(true).setAttribute("openid-disc", discovered);
			// System.err.println("discovered" + discovered);

			// obtain a AuthRequest message to be sent to the OpenID provider
			AuthRequest authReq = manager.authenticate(discovered, returnToUrl);

			// Attribute Exchange example: fetching the 'email' attribute
			FetchRequest fetch = FetchRequest.createFetchRequest();
			SRegRequest sregReq = SRegRequest.createFetchRequest();

			if ("1".equals(httpReq.getParameter("nickname"))) {
				// fetch.addAttribute("nickname",
				// "http://schema.openid.net/contact/nickname", false);
				sregReq.addAttribute("nickname", false);
			}
			if ("1".equals(httpReq.getParameter("email"))) {
				fetch.addAttribute("email",
						"http://schema.openid.net/contact/email", false);
				sregReq.addAttribute("email", false);
			}
			if ("1".equals(httpReq.getParameter("fullname"))) {
				fetch.addAttribute("fullname",
						"http://schema.openid.net/contact/fullname", false);
				sregReq.addAttribute("fullname", false);
			}
			if ("1".equals(httpReq.getParameter("dob"))) {
				fetch.addAttribute("dob",
						"http://schema.openid.net/contact/dob", true);
				sregReq.addAttribute("dob", false);
			}
			if ("1".equals(httpReq.getParameter("gender"))) {
				fetch.addAttribute("gender",
						"http://schema.openid.net/contact/gender", false);
				sregReq.addAttribute("gender", false);
			}
			if ("1".equals(httpReq.getParameter("postcode"))) {
				fetch.addAttribute("postcode",
						"http://schema.openid.net/contact/postcode", false);
				sregReq.addAttribute("postcode", false);
			}
			if ("1".equals(httpReq.getParameter("country"))) {
				fetch.addAttribute("country",
						"http://schema.openid.net/contact/country", false);
				sregReq.addAttribute("country", false);
			}
			if ("1".equals(httpReq.getParameter("language"))) {
				fetch.addAttribute("language",
						"http://schema.openid.net/contact/language", false);
				sregReq.addAttribute("language", false);
			}
			if ("1".equals(httpReq.getParameter("timezone"))) {
				fetch.addAttribute("timezone",
						"http://schema.openid.net/contact/timezone", false);
				sregReq.addAttribute("timezone", false);
			}

			// attach the extension to the authentication request
			if (!sregReq.getAttributes().isEmpty()) {
				authReq.addExtension(sregReq);
			}

			if (!discovered.isVersion2()) {
				// get request
				httpResp.sendRedirect(authReq.getDestinationUrl(true));
				return null;
			} else {
				RequestDispatcher dispatcher = getServletContext()
						.getRequestDispatcher("/formredirection.jsp");
				httpReq.setAttribute("parameterMap", httpReq.getParameterMap());
				httpReq.setAttribute("message", authReq);
				// httpReq.setAttribute("destinationUrl", httpResp
				// .getDestinationUrl(false));
				dispatcher.forward(httpReq, httpResp);
				
				//forwardAuthrequest(httpResp, authReq);
			}

		} catch (OpenIDException e) {
			// present error to the user
			e.printStackTrace();
		}

		return null;
	}

	private void forwardAuthrequest(HttpServletResponse httpResp,
			AuthRequest authReq) {
		try {
			// Construct data
			String data = "";
			int count = 0;
			Set keySet = authReq.getParameterMap().keySet();

			for (Object key : keySet) {
				String name = (key.toString());
				String value = authReq.getParameterValue(name);
				count++;
				//data += URLEncoder.encode(name, "UTF-8") + "="
				//		+ URLEncoder.encode(value, "UTF-8");
				data += name + "=" +value;
				if (count > 0
						&& count < authReq.getParameterMap().keySet()
								.size()) {
					data += "&";
				}
			}
			// data +=
			// "openid-form-redirection="+authReq.getOPEndpoint();
			System.err.println("DATA:" + data);
			System.err.println("ENDPOINT"+authReq.getDestinationUrl(false));
			// Send data
			URL url = new URL(authReq.getOPEndpoint());
			
			HttpURLConnection conn = (HttpURLConnection)url.openConnection();
			conn.setRequestMethod("POST");
			conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			conn.setDoInput(true);
			conn.setDoOutput(true);
			OutputStreamWriter wr = new OutputStreamWriter(conn
					.getOutputStream());
			wr.write(data);
			wr.flush();
			// Get the response
			BufferedReader rd = new BufferedReader(
					new InputStreamReader(conn.getInputStream()));
			String line;
			while ((line = rd.readLine()) != null) { // write response
				httpResp.getWriter().write(line);
			}
			wr.close();
			rd.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * processing the authentication response from the OpenId provider.
	 * 
	 * @param httpReq
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public Identifier verifyResponse(HttpServletRequest httpReq)
			throws UnsupportedEncodingException {
		try {
			// extract the parameters from the authentication response
			// (which comes in as a HTTP request from the OpenID provider)
			ParameterList response = new ParameterList(httpReq
					.getParameterMap());

			// retrieve the previously stored discovery information
			DiscoveryInformation discovered = (DiscoveryInformation) httpReq
					.getSession().getAttribute("openid-disc");
			// extract the receiving URL from the HTTP request
			StringBuffer receivingURL = httpReq.getRequestURL();
			String queryString = httpReq.getQueryString();
			if (queryString != null && queryString.length() > 0)
				receivingURL.append("?").append(httpReq.getQueryString());
			// verify the response; ConsumerManager needs to be the same
			// (static) instance used to place the authentication request
			VerificationResult verification = manager.verify(receivingURL
					.toString(), response, discovered);

			// examine the verification result and extract the verified
			// identifier
			Identifier verified = verification.getVerifiedId();
			if (verified != null) {
				AuthSuccess authSuccess = (AuthSuccess) verification
						.getAuthResponse();

				if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
					MessageExtension ext = authSuccess
							.getExtension(SRegMessage.OPENID_NS_SREG);
					if (ext instanceof SRegResponse) {
						SRegResponse sregResp = (SRegResponse) ext;
						for (Iterator iter = sregResp.getAttributeNames()
								.iterator(); iter.hasNext();) {
							String name = (String) iter.next();
							String value = sregResp.getParameterValue(name);
							httpReq.setAttribute(name, value);
						}
					}
				}
				if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
					FetchResponse fetchResp = (FetchResponse) authSuccess
							.getExtension(AxMessage.OPENID_NS_AX);

					// List emails = fetchResp.getAttributeValues("email");
					// String email = (String) emails.get(0);

					List aliases = fetchResp.getAttributeAliases();
					for (Iterator iter = aliases.iterator(); iter.hasNext();) {
						String alias = (String) iter.next();
						List values = fetchResp.getAttributeValues(alias);
						if (values.size() > 0) {
							httpReq.setAttribute(alias, values.get(0));
						}
					}
				}
				return verified; // success
			}
		} catch (OpenIDException e) {
			e.printStackTrace();
			log.error(e.getMessage());
		}

		return null;
	}

}
